﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.IO;
using System.Net;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using Argos.Design;
using Argos.Net;
using System.Threading;


namespace Argos.Activities
{
	/// <summary>
	/// This activity is used to perform an HTTP request to a web resource. After
	/// the activity has executed, you can access the result from the
	/// <see cref="HtmlContent"/> and <see cref="Status"/> properties.
	/// </summary>
	[Description("Performs a HTTP request to the specified URL using the specified settings.")]
	public partial class HttpActivity: Activity
	{

		#region #ctor: public HttpActivity()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public HttpActivity()
		{
			InitializeComponent();

			this.FollowRedirection = true;
		}

		#endregion



		#region public byte[] BinaryContent { get; set; }

		public static readonly DependencyProperty BinaryContentProperty = DependencyProperty.Register("BinaryContent", typeof(byte[]), typeof(HttpActivity));

		[Description("Contains the content from the page specified in 'Url' as a byte array.")]
		[DefaultValue(null)]
		[Browsable(false)]
		public byte[] BinaryContent
		{
			get { return (byte[])this.GetValue(BinaryContentProperty); }
			set { this.SetValue(BinaryContentProperty, value); }
		}

		#endregion

		#region public string ContentType { get; set; }

		public static readonly DependencyProperty ContentTypeProperty = DependencyProperty.Register("ContentType", typeof(string), typeof(HttpActivity));

		[Description("Contains the content type sent by the server for the resource specified in 'Url'.")]
		[DefaultValue(null)]
		[Browsable(false)]
		public string ContentType
		{
			get { return (string)this.GetValue(ContentTypeProperty); }
			set { this.SetValue(ContentTypeProperty, value); }
		}

		#endregion

		#region public Credential Credentials { get; set; }

		public static readonly DependencyProperty CredentialsProperty = DependencyProperty.Register("Credentials", typeof(Credential), typeof(HttpActivity));

		/// <summary>
		/// Sets or returns the credentials to use when accessing the resource specified in <see cref="Url"/>.
		/// </summary>
		[Category("Http")]
		[Description("Specifies the credentials used to access the resource specified in 'Url'.")]
		[DefaultValue(null)]
		[TypeConverter(typeof(ExpandableObjectConverter))]
		public Credential Credentials
		{
			get
			{
				Credential c = (Credential)this.GetValue(HttpActivity.CredentialsProperty);
				if (null == c)
				{
					c = new Credential();
					this.SetValue(CredentialsProperty, c);
				}
				return c;
			}
			set { this.SetValue(HttpActivity.CredentialsProperty, value); }
		}

		#endregion

		#region public bool FollowRedirection { get; set; }

		public static readonly DependencyProperty FollowRedirectionProperty = DependencyProperty.Register("FollowRedirection", typeof(Boolean), typeof(HttpActivity));

		[Category("Http")]
		[Description("Specifies whether the activity should automatically follow any redirection response sent by the server for the resource specified in 'Url'.")]
		[DefaultValue(true)]
		public bool FollowRedirection
		{
			get { return (bool)this.GetValue(FollowRedirectionProperty); }
			set { this.SetValue(FollowRedirectionProperty, value); }
		}

		#endregion

		#region public string HtmlContent { get; set; }

		public static readonly DependencyProperty HtmlContentProperty = DependencyProperty.Register("HtmlContent", typeof(string), typeof(HttpActivity));

		/// <summary>
		/// Returns the HTML content returned from the resource specified in <see cref="Url"/>.
		/// </summary>
		[Description("Contains the HTML content from the page specified in 'Url'.")]
		[DefaultValue(null)]
		[Browsable(false)]
		public string HtmlContent
		{
			get { return (string)this.GetValue(HttpActivity.HtmlContentProperty); }
			protected set { this.SetValue(HttpActivity.HtmlContentProperty, value); }
		}

		#endregion

		#region public DateTime? LastModifiedUtc { get; set; }

		public static readonly DependencyProperty LastModifiedUtcProperty = DependencyProperty.Register("LastModifiedUtc", typeof(DateTime?), typeof(HttpActivity));

		[Description("Contains the last modified date for the resource specified in 'Url' sent by the server. The date and time is in UTC.")]
		[DefaultValue(null)]
		[Browsable(false)]
		public DateTime? LastModifiedUtc
		{
			get { return (DateTime?)this.GetValue(LastModifiedUtcProperty); }
			set
			{
				if (value.HasValue && value.Value.Kind != DateTimeKind.Utc)
				{
					value = value.Value.ToUniversalTime();
				}

				this.SetValue(LastModifiedUtcProperty, value);
			}
		}

		#endregion

		#region public Uri ResponseUrl { get; set; }

		public static readonly DependencyProperty ResponseUrlProperty = DependencyProperty.Register("ResponseUrl", typeof(Uri), typeof(HttpActivity));

		/// <summary>
		/// Sets or returns the URL that sent the response to the request.
		/// </summary>
		[Browsable(false)]
		public Uri ResponseUrl
		{
			get { return (Uri)this.GetValue(ResponseUrlProperty); }
			set { this.SetValue(ResponseUrlProperty, value); }
		}

		#endregion
		
		#region public HttpStatusCode Status { get; protected set; }

		public static readonly DependencyProperty StatusProperty = DependencyProperty.Register("Status", typeof(HttpStatusCode), typeof(HttpActivity));

		/// <summary>
		/// Returns the status code for accessing the resource specified in <see cref="Url"/>.
		/// </summary>
		[Description("Specifies the status of the HTTP request performed by this activity.")]
		[Browsable(false)]
		public HttpStatusCode Status
		{
			get { return (HttpStatusCode)this.GetValue(HttpActivity.StatusProperty); }
			protected set { this.SetValue(HttpActivity.StatusProperty, value); }
		}

		#endregion

		#region public int RequestTimeout { get; set; }

		/// <summary>
		/// Sets or returns the timeout in milliseconds to wait for the response to the request.
		/// </summary>
		/// <remarks>
		/// If you set this property to zero, no timeout will be set on the request.
		/// </remarks>
		[Browsable(false)]
		public int RequestTimeout
		{
			get { return (int)GetValue(RequestTimeoutProperty); }
			set
			{
				if (value < 0)
				{
					throw new ArgumentException("The timeout cannot be set to less than zero.", "RequestTimeout");
				}
				SetValue(RequestTimeoutProperty, value);
			}
		}

		public static readonly DependencyProperty RequestTimeoutProperty =
			DependencyProperty.Register("RequestTimeout", typeof(int), typeof(HttpActivity));

		#endregion
		
		#region public Uri Url { get; set; }

		public static readonly DependencyProperty UrlProperty = DependencyProperty.Register("Url", typeof(Uri), typeof(HttpActivity));

		/// <summary>
		/// Sets or returns the URL for the resource to access in the activity.
		/// </summary>
		/// <remarks>
		/// This property can only be set to a URL that begins with <c>http://</c> or <c>https://</c>.
		/// </remarks>
		/// <exception cref="ArgumentException">The exception that is thrown if this property is set to a URL that does not begin with <c>http://</c> or <c>https://</c>.</exception>
		[Category("Http")]
		[Description("Specifies the URL for the resource to access in the activity.")]
		[DefaultValue(null)]
		[ValidationOption(ValidationOption.Required)]
		[Editor(typeof(UriTypeEditor), typeof(UITypeEditor))]
		public Uri Url
		{
			get { return (Uri)this.GetValue(HttpActivity.UrlProperty); }
			set
			{
				if (null != value)
				{
					if (!value.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && !value.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
					{
						throw new ArgumentException("The 'Url' property must be set to a value beginning with either 'http' or 'https'.");
					}
				}
				this.SetValue(HttpActivity.UrlProperty, value);
			}
		}

		#endregion

		#region public bool UseDefaultCredentials { get; set; }

		public static readonly DependencyProperty UseDefaultCredentialsProperty = DependencyProperty.Register("UseDefaultCredentials", typeof(bool), typeof(HttpActivity));

		/// <summary>
		/// Sets or returns whether to use default credentials when accessing the
		/// resource specified in <see cref="Url"/>.
		/// </summary>
		/// <remarks>
		/// Default credentials are the credentials of the security context in which
		/// the code is running.
		/// </remarks>
		[Category("Http")]
		[Description("Specifies whether to use default credentials when accessing the resource specified in 'Url'. Default credentials are the Windows credentials of the hosting process that is running the activity.")]
		[DefaultValue(false)]
		public bool UseDefaultCredentials
		{
			get { return (bool)this.GetValue(HttpActivity.UseDefaultCredentialsProperty); }
			set { this.SetValue(HttpActivity.UseDefaultCredentialsProperty, value); }
		}

		#endregion



		/// <summary>
		/// Sets or returns the last exception that occured in the activity.
		/// </summary>
		protected Exception LastException { get; set; }

		/// <summary>
		/// When overridden in a derived class, specifies whether to throw any
		/// exception that occurs during the execution of the activity.
		/// </summary>
		protected virtual bool ThrowException { get { return false; } }



		#region protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)

		/// <summary>
		/// Executes the HTTP request and populates the <see cref="HtmlContent"/> and <see cref="Status"/>
		/// properties.
		/// </summary>
		protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
		{
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Url);
			request.AllowAutoRedirect = this.FollowRedirection;

			if (this.UseDefaultCredentials)
			{
				request.UseDefaultCredentials = true;
			}
			else if (null != this.Credentials)
			{
				request.Credentials = this.Credentials;
			}

			HttpWebResponse response = null;
			try
			{
				if (this.RequestTimeout > 0)
					request.Timeout = this.RequestTimeout;

				response = (HttpWebResponse)request.GetResponse();
				this.ContentType = response.ContentType;
				this.ResponseUrl = response.ResponseUri;

				string lastModified = response.Headers[HttpResponseHeader.LastModified];

				DateTime dt;
				if (DateTime.TryParse(lastModified, System.Globalization.CultureInfo.GetCultureInfo("en-US"), System.Globalization.DateTimeStyles.AssumeUniversal, out dt))
				{
					this.LastModifiedUtc = dt.ToUniversalTime();
				}

				Encoding responseEncoding = Encoding.UTF8;
				if (!string.IsNullOrEmpty(response.CharacterSet))
				{
					try
					{
						responseEncoding = Encoding.GetEncoding(response.CharacterSet);
					}
					catch { }
				}

				using (Stream strm = response.GetResponseStream())
				{
					List<Byte> buffer = new List<byte>();

					int b = strm.ReadByte();
					while (b >= 0)
					{
						buffer.Add((byte)b);
						b = strm.ReadByte();
					}

					this.BinaryContent = new byte[buffer.Count];
					buffer.CopyTo(this.BinaryContent);

					this.HtmlContent = responseEncoding.GetString(this.BinaryContent);
				}

				this.Status = response.StatusCode;
			}
			catch (WebException ex)
			{
				this.LastException = ex;

				if (null != ex.Response)
				{
					response = (HttpWebResponse)ex.Response;
					this.Status = response.StatusCode;
				}

				if (this.ThrowException)
				{
					throw ex;
				}
			}
			
			return ActivityExecutionStatus.Closed;
		}

		#endregion

	}
}
